;; 切换主题的函数
;; From: gitee.com/mickey991/emacs-config/blob/master/init.el#L296
;;;###autoload
(defun +ui/switch-theme (theme)
  "Disable active themes and load THEME."
  (interactive (list (intern (completing-read "Theme: "
                                              (->> (custom-available-themes)
                                                   (-map #'symbol-name))))))
  (mapc #'disable-theme custom-enabled-themes)
  (load-theme theme 'no-confirm))

;;;###autoload
(defun +edit/backward-kill-line ()
  "Delete the text before the cursor in the current line and keep indentation."
  (interactive)
  (let ((start (save-excursion
                 (back-to-indentation)
                 (point)))
        (cursor (point)))
    (if (bolp)
        ;; If cursor is at the beginning of the line, perform the standard backspace action
        (delete-backward-char 1)
      (if (<= cursor start)
          ;; If cursor is at or before the indentation, delete to the beginning of the line
          (delete-region (line-beginning-position) cursor)
        ;; Otherwise, delete from the indentation to the cursor
        (delete-region start cursor)))))

;;;###autoload
(defun +edit/open-line-below ()
  "Open a line below the current one, similar to `o' in Vim."
  (interactive)
  (end-of-line) ; Move cursor to end of line
  (newline-and-indent)) ; Insert newline and indent new line appropriately

;;;###autoload
(defun +edit/crux-move-beginning-of-line (arg)
  "Move point to the first non-whitespace character on this line.
If point is already there, move to the beginning of the line.
With ARG, move forward ARG - 1 lines first."
  (interactive "^p")
  (setq arg (or arg 1))
  ;; Move lines first if ARG is specified
  (unless (= arg 1)
    (let ((line-move-visual nil))
      (forward-line (1- arg))))
  (let ((orig-point (point)))
    (+edit--crux-move-to-mode-line-start)
    (when (= orig-point (point))
      (beginning-of-line))))

;;;###autoload
(defun +vertico/vertico-insert ()
  "Make vertico and vertico-directory behave more like ivy|ido"
  (interactive)
  (let* ((mb (minibuffer-contents-no-properties))
         (lc (if (string= mb "") mb (substring mb -1))))
    (cond ((string-match-p "^[/~:]" lc) (self-insert-command 1 ?/))
          ((file-directory-p (vertico--candidate)) (vertico-insert))
          (t (self-insert-command 1 ?/)))))

;;;###autoload
(defun +window/split-window-right ()
  "Split window with another buffer."
  (interactive)
  (select-window (split-window-right))
  (switch-to-buffer (other-buffer)))

;;;###autoload
(defun +window/split-window-below ()
  "Split window with another buffer."
  (interactive)
  (select-window (split-window-below))
  (switch-to-buffer (other-buffer)))

;; From: emacswiki.org/emacs/ToggleWindowSplit
;;;###autoload
(defun +window/toggle-window-split ()
  "
        A
A|B <-> -
        B
"
  (interactive)
  (if (= (count-windows) 2)
      (let* ((this-win-buffer (window-buffer))
             (next-win-buffer (window-buffer (next-window)))
             (this-win-edges (window-edges (selected-window)))
             (next-win-edges (window-edges (next-window)))
             (this-win-2nd (not (and (<= (car this-win-edges)
                                         (car next-win-edges))
                                     (<= (cadr this-win-edges)
                                         (cadr next-win-edges)))))
             (splitter
              (if (= (car this-win-edges)
                     (car (window-edges (next-window))))
                  'split-window-horizontally
                'split-window-vertically)))
        (delete-other-windows)
        (let ((first-win (selected-window)))
          (funcall splitter)
          (if this-win-2nd (other-window 1))
          (set-window-buffer (selected-window) this-win-buffer)
          (set-window-buffer (next-window) next-win-buffer)
          (select-window first-win)
          (if this-win-2nd (other-window 1))))))

;; From: https://blog.chmouel.com/posts/emacs-isearch/
;;;###autoload
(defun +isearch/project-search-from-isearch ()
  "Use the current `isearch' string to perform a project-wide search via `project-find-regexp'."
  (interactive)
  (when (and (bound-and-true-p isearch-mode)
             (not (string-empty-p isearch-string)))
    (let* ((regexp isearch-regexp)
           ;; Use regexp as-is, or escape normal string
           (query (if regexp
                      isearch-string
                    (regexp-quote isearch-string))))
      ;; Add to isearch ring (history)
      (isearch-update-ring isearch-string regexp)
      ;; Cleanly exit isearch mode
      (let ((search-nonincremental-instead nil))
        (ignore-errors
          (isearch-done)
          (isearch-clean-overlays)))
      ;; Trigger project-wide search
      (project-find-regexp query))))

;;;###autoload
(defun +isearch/occur-from-isearch ()
  "Use current `isearch' string to perform `occur' search."
  (interactive)
  (when (and (bound-and-true-p isearch-mode)
             (not (string-empty-p isearch-string)))
    (let* ((regexp isearch-regexp)
           (query (if regexp
                      isearch-string
                    (regexp-quote isearch-string))))
      ;; Add to history
      (isearch-update-ring isearch-string regexp)
      ;; Exit isearch safely
      (let ((search-nonincremental-instead nil))
        (ignore-errors
          (isearch-done)
          (isearch-clean-overlays)))
      ;; Launch occur
      (occur query))))

;; From: https://pengpengxp.github.io/emacs/emacs_isearch_summary.html
;;;###autoload
(defmacro isearch-quit-and-run (&rest body)
  "Quit isearch and run BODY after a delay."
  (declare (indent 0))
  `(progn
     (put 'quit 'error-message "")
     (run-at-time nil nil
                  (lambda ()
                    (put 'quit 'error-message "Quit")
                    (with-demoted-errors "Error: %S"
                      ,@body)))
     (isearch-cancel)))

;;;###autoload
(defun +isearch/peng-rerun-isearch ()
  "Rerun isearch from the original place."
  (interactive)
  (isearch-quit-and-run
    (isearch-forward)))

;;;###autoload
(defun +dict/sqlite-query-word ()
  "Query word translation from SQLite database using Emacs native sqlite library."
  (interactive)
  (let* ((raw-word (thing-at-point 'word t))
         (word (and raw-word (downcase (replace-regexp-in-string "[^A-Za-z]" "" raw-word))))
         (pos (point))
         (db-path (expand-file-name "dict/stardict.db" no-littering-etc-directory)))
    (when (and word (not (string-empty-p word)))
      (+dict--play-word-audio word)
      (unless +dict--db
        (setq +dict--db (sqlite-open db-path)))
      (condition-case err
          (let* ((query "SELECT word, phonetic, translation FROM stardict WHERE word = ? LIMIT 1;")
                 (result (sqlite-select +dict--db query (list word) nil)))
            (pcase result
              (`((,w ,ph ,trans ))
               (+dict--show-posframe
                (format "%s\n%s\n%s"
                        w
                        (if (string-empty-p ph) "" (format "/%s/" ph))
                        trans)
                pos))
              (_
               (+dict--show-posframe "(｡ŏ_ŏ) No result found..." pos))))
        (error
         (message "Function \"+dict/sqlite-query-word\" error: %s" err))))))

;;;###autoload
(defun +fanyi/fanyi-selected-text ()
  "Translate the selected text using the `fanyi-dwim' function."
  (interactive)
  (require 'fanyi)
  (let ((word (buffer-substring-no-properties (region-beginning) (region-end)))) ; 获取选中文本
    (fanyi-dwim word))
  (deactivate-mark))

;;;###autoload
(defun +fanyi/fanyi-at-point ()
  "Translate the word at point using `fanyi-dwim' function."
  (interactive)
  (require 'fanyi)
  (let ((word (thing-at-point 'word t)))
    (fanyi-dwim word)))

;; From: emacs.stackexchange.com/a/75613
;;;###autoload
(defun +eshell/clear ()
  "Clear all content on the eshell screen."
  (interactive)
  (let ((has-input (> (point) eshell-last-output-end)))
    (when has-input
      (end-of-buffer)
      (eshell-kill-input))
    (insert "clear 1")
    (eshell-send-input)
    (when has-input
      (end-of-buffer)
      (eshell-bol)
      (yank))))

;; From: emacs-china.org/t/eshell/14314/23?u=harl
;;;###autoload
(defun +eshell/close (arg)
  "Delete a character or quit eshell if there's nothing to delete."
  (interactive "p")
  (if (and (eolp) (looking-back eshell-prompt-regexp nil))
      (eshell-life-is-too-much)
    (delete-char arg)))

;;;###autoload
(defun +org/org-insert-header ()
  "Insert header into the org file, including title, date, last_modified"
  (interactive)
  (goto-char (point-min))
  (let ((filename (file-name-sans-extension (file-name-nondirectory (buffer-file-name))))
        (current-date (format-time-string "[%Y-%m-%d %a %H:%M]")))
    ;; (insert "# -*- org-image-actual-width: nil; -*-\n") ; 这一行表示在org中插入的图片不使用其自身的宽度，也可以使用 setq 添加到elisp中
    ;; (insert "#+startup: latexpreview \n\")
    ;; (insert "#+last_modified: \n")
    (insert (format "#+title: %s\n" filename))
    (insert (format "#+date: %s\n" current-date))
    (insert (format "#+author: Le\n\n"))))

;;;###autoload
(defun +org/org-preview-image-link-in-posframe (point)
  "Preview the image at the org-link where the cursor is located in a posframe."
  (interactive "d")
  (let* ((element (org-element-context))
         (link-type (org-element-property :type element))
         (path (org-element-property :path element))
         (tmp-buf " *org-img-preview*"))  ;; Temporary buffer name as a local variable
    (if (and (string= link-type "file") path (file-exists-p path))
        (let ((image-buffer (+posframe--image-preview-in-posframe (expand-file-name path) nil nil tmp-buf)))
          (when image-buffer
            (unwind-protect
                (read-key "Press any key to close...")
              (posframe-delete image-buffer))))
      (message "No valid image link at point."))))

;;;###autoload
(defun +org/org-select-images ()
  "Select an image from './.assets' and insert it as an Org link."
  (interactive)
  (let* ((current-dir (file-name-directory (or (buffer-file-name) default-directory)))
         (default-dir (expand-file-name ".assets/" current-dir))
         (image-file (read-file-name "Select an image: " default-dir nil t))
         (relative-path (file-relative-name image-file (file-name-directory (buffer-file-name)))))
    (when (and image-file (not (string-empty-p image-file)))
      (insert (format "[[file:%s]]" relative-path)))))

;; Inspired by: https://github.com/Elilif/.elemacs/blob/ff4f2e3076de5aa653479f37b77d294940d0a828/lib/lib-embark.el
;; (require 'posframe)
;; (require 'embark)

(defun +tools--image-preview ()
  "Preview image in a posframe while selecting a file in the minibuffer."
  (let* ((target (embark--targets))
         (file-path (plist-get (car target) :target))
         (name (file-name-nondirectory file-path))
         (mode (assoc-default name auto-mode-alist #'string-match)))
    (posframe-hide-all)
    (when (and file-path (file-exists-p file-path) (memq mode '(image-mode)))
      (+posframe--image-preview-in-posframe file-path nil nil nil))))

(defun +tools--remove-preview ()
  "Remove image preview and clean up posframe buffers.
TMP-BUF is the temporary buffer name used to display the image."
  (remove-hook 'post-command-hook #'+tools/image-preview)
  (posframe-delete " *tmp-buf*"))  ;; Fallback to default if tmp-buf is nil

(advice-add '+org/select-images :before
            (lambda (&rest _args)
              (add-hook 'post-command-hook #'+tools--image-preview)
              (add-hook 'minibuffer-exit-hook #'+tools--remove-preview)))

;;;###autoload
(defun +org/org-download-clipboard-windows ()
  "Save image from clipboard into `.assets/` on Windows."
  (interactive)
  (unless (executable-find "magick")
    (user-error "ImageMagick (magick) not found in PATH"))
  (let* ((imgdir (concat (file-name-directory (buffer-file-name)) ".assets/"))
         (basename (format "screenshot-%s.png" (format-time-string "%Y%m%d-%H%M%S")))
         (filename (expand-file-name basename imgdir)))
    (make-directory imgdir t)
    ;; 将路径统一为正斜杠
    (setq filename (replace-regexp-in-string "\\\\" "/" filename))
    ;; 用 ImageMagick 保存剪贴板
    (cond
     ((not (eq (call-process "magick" nil nil nil "clipboard:" filename) 0))
      (message "Failed to read clipboard via ImageMagick."))
     (t
      (ignore-errors
        (org-download-insert-link filename filename))
      (message "Image inserted from clipboard into %s" filename)))))

;;;###autoload
(defun +flymake/copy-flymake-diagnostic-at-point ()
  "Copy Flymake diagnostics at point to kill ring."
  (interactive)
  (let* ((diags (flymake-diagnostics (point) (point)))
         (text (string-join
                (mapcar #'flymake-diagnostic-text diags)
                "\n")))
    (when text
      (kill-new text)
      (message "Flymake info copied: %s" text))))

;;;###autoload
(defun +compile/update-command ()
  "Force update `compile-command` based on current buffer."
  (interactive)
  (+compile/c-compile-setup)
  (message "Updated compile command: %s" compile-command))
